﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="KeystrokeEngine.cs" company="arvystate.net">
//   arvystate.net
// </copyright>
// <summary>
//   Engine processes gesture and sends mapped keystroke.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace PresentationAid.Lib.GestureEngines
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows.Input;

    using NLog;

    using PresentationAid.Lib.Enums;
    using PresentationAid.Lib.GestureEngines.GestureSettings;

    /// <summary>
    /// Engine processes gesture and sends mapped keystroke.
    /// </summary>
    public class KeystrokeEngine : GestureEngine
    {
        #region Fields

        /// <summary>
        /// The key mapping.
        /// </summary>
        protected readonly Dictionary<GestureCommand, Key> KeyMapping;

        #endregion

        #region Static Fields

        /// <summary>
        /// The logger.
        /// </summary>
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="KeystrokeEngine"/> class.
        /// </summary>
        public KeystrokeEngine()
        {
            Logger.Trace("Init: KeystrokeEngine");

            this.KeyMapping = new Dictionary<GestureCommand, Key>();

            if (this.GetType() != typeof(KeystrokeEngine))
            {
                return;
            }

            Logger.Trace("InitDetail: KeystrokeEngine - CreateSettings");

            this.EngineSettings = new KeystrokeSettings(this);
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// Clears currently mapped gesture command
        /// </summary>
        /// <param name="gesture">
        /// Gesture to clear
        /// </param>
        public void ClearMapping(GestureCommand gesture)
        {
            if (this.KeyMapping.ContainsKey(gesture))
            {
                Logger.Trace("Method: ClearMapping - Gesture: {0}", gesture.ToString());

                this.KeyMapping.Remove(gesture);
            }
        }

        /// <summary>
        /// Gets mapping for gesture command
        /// </summary>
        /// <param name="gesture">
        /// Gesture to get mapping for
        /// </param>
        /// <returns>
        /// Key that is mapped to gesture <see cref="Key"/>.
        /// </returns>
        public Key GetMapping(GestureCommand gesture)
        {
            return this.KeyMapping.ContainsKey(gesture) ? this.KeyMapping[gesture] : Key.None;
        }

        /// <summary>
        /// Returns mapping in string format
        /// </summary>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        public override string GetMapping()
        {
            StringBuilder mapping = new StringBuilder();

            // Add control gesture
            mapping.Append(this.ControlGesture);
            mapping.Append(":TakeControl;");

            foreach (KeyValuePair<GestureCommand, Key> pair in this.KeyMapping)
            {
                if (pair.Key == this.ControlGesture)
                {
                    continue;
                }

                mapping.Append(pair.Key + ":" + pair.Value + ";");
            }

            // Remove last semicolon
            mapping.Remove(mapping.Length - 1, 1);

            Logger.Debug("Method: GetMapping - Return: {0}", mapping.ToString());

            return mapping.ToString();
        }

        /// <summary>
        /// SlideShow command only supported for PowerPoint, so we are testing if mapping is default
        /// </summary>
        /// <param name="gesture">
        /// Gesture to be checked.
        /// </param>
        /// <returns>
        /// SlideShowCommand of the mapped key <see cref="SlideShowCommand"/>.
        /// </returns>
        public override SlideShowCommand GetSlideShowCommand(GestureCommand gesture)
        {
            Key mappedKey = this.GetMapping(gesture);

            // Return Command of based on PowerPoint keyboard shortcuts, do not support
            // EndSlideshow
            switch (mappedKey)
            {
                case Key.F5:
                    return SlideShowCommand.StartStopSlideshow;
                case Key.Back:
                case Key.Left:
                case Key.Up:
                case Key.PageUp:
                case Key.P:
                    return SlideShowCommand.PreviousSlide;
                case Key.Right:
                case Key.Down:
                case Key.Space:
                case Key.PageDown:
                case Key.N:
                case Key.Enter:
                    return SlideShowCommand.NextSlide;
            }

            return SlideShowCommand.None;
        }

        /// <summary>
        /// Method parses mapping in a string to keystroke commands
        /// </summary>
        /// <param name="map">
        /// Keystroke mapping as a string
        /// </param>
        /// <returns>
        /// True if successful <see cref="bool"/>.
        /// </returns>
        public override bool ParseMapping(string map = null)
        {
            if (!string.IsNullOrEmpty(map))
            {
                Logger.Debug("Method: ParseMapping - Map: {0}", map);

                try
                {
                    this.KeyMapping.Clear();

                    string[] mappings = map.Split(';');

                    foreach (string[] keyCommand in mappings.Select(command => command.Split(':')))
                    {
                        if (keyCommand[1].Trim() == "TakeControl")
                        {
                            this.ControlGesture = (GestureCommand)Enum.Parse(typeof(GestureCommand), keyCommand[0]);
                        }
                        else
                        {
                            this.SetMapping(
                                (GestureCommand)Enum.Parse(typeof(GestureCommand), keyCommand[0]), 
                                (Key)Enum.Parse(typeof(Key), keyCommand[1]));
                        }
                    }

                    return true;
                }
                catch (Exception ex)
                {
                    Logger.WarnException("Method: ParseMapping", ex);
                }
            }

            this.SetDefaultMapping();

            return false;
        }

        /// <summary>
        /// Sets default mapping
        /// </summary>
        public override void SetDefaultMapping()
        {
            Logger.Debug("Method: SetDefaultMapping");

            base.SetDefaultMapping();

            // Keystroke has default mapping
            this.SetMapping(GestureCommand.Wave, Key.F5);
            this.SetMapping(GestureCommand.SwipeRight, Key.Right);
            this.SetMapping(GestureCommand.SwipeLeft, Key.Left);
        }

        /// <summary>
        /// Sets mapping for gesture command to certain key
        /// </summary>
        /// <param name="gesture">
        /// Gesture to set mapping for
        /// </param>
        /// <param name="key">
        /// Key to set mapping to
        /// </param>
        public void SetMapping(GestureCommand gesture, Key key)
        {
            Logger.Trace("Method: SetMapping - Gesture: {0}, Key: {1}", gesture.ToString(), key.ToString());

            if (key == Key.None)
            {
                this.ClearMapping(gesture);
            }

            if (this.KeyMapping.ContainsKey(gesture))
            {
                this.KeyMapping[gesture] = key;
            }
            else
            {
                this.KeyMapping.Add(gesture, key);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Executes key action
        /// </summary>
        /// <param name="gesture">
        /// Gesture to execute
        /// </param>
        /// <returns>
        /// True on success <see cref="bool"/>.
        /// </returns>
        protected override bool ExecuteSpecificGesture(GestureCommand gesture)
        {
            if (this.KeyMapping.ContainsKey(gesture))
            {
                // Send key
                Logger.Trace("Method: ExecuteSpecificGesture: Gesture: {0}", gesture.ToString());

                System.Windows.Forms.SendKeys.SendWait(this.KeystrokeToCommandString(this.KeyMapping[gesture]));

                return true;
            }

            return false;
        }

        /// <summary>
        /// The keystroke to command string.
        /// </summary>
        /// <param name="key">
        /// The key.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        protected string KeystrokeToCommandString(Key key)
        {
            switch (key)
            {
                    // Generic action keys
                case Key.F1:
                case Key.F2:
                case Key.F3:
                case Key.F4:
                case Key.F5:
                case Key.F6:
                case Key.F7:
                case Key.F8:
                case Key.F9:
                case Key.F10:
                case Key.F11:
                case Key.F12:

                case Key.Tab:

                case Key.Left:
                case Key.Up:
                case Key.Right:
                case Key.Down:

                case Key.Delete:
                case Key.Insert:
                case Key.End:
                case Key.Home:

                case Key.Add:
                case Key.Subtract:
                case Key.Multiply:
                case Key.Divide:

                case Key.Enter:

                case Key.Help:

                case Key.NumLock:
                    return "{" + key.ToString().ToUpper() + "}";
                case Key.D0:
                case Key.D1:
                case Key.D2:
                case Key.D3:
                case Key.D4:
                case Key.D5:
                case Key.D6:
                case Key.D7:
                case Key.D8:
                case Key.D9:

                case Key.NumPad0:
                case Key.NumPad1:
                case Key.NumPad2:
                case Key.NumPad3:
                case Key.NumPad4:
                case Key.NumPad5:
                case Key.NumPad6:
                case Key.NumPad7:
                case Key.NumPad8:
                case Key.NumPad9:
                    return key.ToString().ToUpper().Substring(key.ToString().Length - 1, 1);
                case Key.Back:
                    return "{BACKSPACE}";
                case Key.Escape:
                    return "{ESC}";
                case Key.Space:
                    return " ";
                case Key.Scroll:
                    return "{SCROLLLOCK}";
                case Key.PageUp:
                    return "{PGUP}";
                case Key.PageDown:
                    return "{PGDN}";
                case Key.PrintScreen:
                    return "{PRTSC}";
                default:
                    return key.ToString().ToUpper();
            }
        }

        #endregion
    }
}